using System.Collections.Generic;
using System.IO;
using NFreeMarker.Template;

namespace NFreeMarker.Core
{
    /**
     * @version $Id: BlockAssignment.java,v 1.4 2004/07/07 21:11:12 szegedia Exp $
     */
    internal sealed class BlockAssignment : TemplateElement
    {
        private readonly string _varName;
        private readonly Expression _namespaceExp;
        private readonly AssignmentScope _scope;

        internal BlockAssignment(TemplateElement nestedBlock, string varName, AssignmentScope scope, Expression namespaceExp)
        {
            NestedBlock = nestedBlock;
            _varName = varName;
            _namespaceExp = namespaceExp;
            _scope = scope;
        }

        internal override void Accept(Environment env)
        {
            if (NestedBlock != null)
            {
                env.Visit(NestedBlock, new CaptureOutput(this, env), null);
            }
            else
            {
                ITemplateModel value = new SimpleScalar("");
                if (_namespaceExp != null)
                {
                    var ns = (Environment.Namespace) _namespaceExp.GetAsTemplateModel(env);
                    ns.Put(_varName, value);
                }
                else
                {
                    switch (_scope)
                    {
                        case AssignmentScope.Namespace:
                            env.SetVariable(_varName, value);
                            break;
                        case AssignmentScope.Global:
                            env.SetGlobalVariable(_varName, value);
                            break;
                        case AssignmentScope.Local:
                            env.SetLocalVariable(_varName, value);
                            break;
                    }
                }
            }
        }

        private class CaptureOutput : ITemplateTransformModel
        {
            private readonly BlockAssignment _assignment;
            private readonly Environment _env;
            private readonly Environment.Namespace _fnsModel;

            public CaptureOutput(BlockAssignment assignment, Environment env)
            {
                _assignment = assignment;
                _env = env;
                ITemplateModel nsModel = null;
                if (assignment._namespaceExp != null)
                {
                    nsModel = assignment._namespaceExp.GetAsTemplateModel(env);
                    if (!(nsModel is Environment.Namespace))
                    {
                        throw new TemplateException(
                            "namespace parameter does not specify "
                            + "a namespace. It is a "
                            + nsModel.GetType().FullName, env);
                    }
                }
                _fnsModel = (Environment.Namespace) nsModel;
            }

            public TextWriter GetWriter(TextWriter output, IDictionary<string, ITemplateModel> args)
            {
                return new CaptureOutputWriter(this);
            }

            private class CaptureOutputWriter : StringWriter
            {
                private readonly CaptureOutput _captureOutput;

                public CaptureOutputWriter(CaptureOutput captureOutput)
                {
                    _captureOutput = captureOutput;
                }

                public override void Close()
                {
                    var result = new SimpleScalar(ToString());
                    switch (_captureOutput._assignment._scope)
                    {
                        case AssignmentScope.Namespace:
                            {
                                if (_captureOutput._fnsModel != null)
                                {
                                    _captureOutput._fnsModel.Put(_captureOutput._assignment._varName, result);
                                }
                                else
                                {
                                    _captureOutput._env.SetVariable(_captureOutput._assignment._varName, result);
                                }
                                break;
                            }
                        case AssignmentScope.Local:
                            {
                                _captureOutput._env.SetLocalVariable(_captureOutput._assignment._varName, result);
                                break;
                            }
                        case AssignmentScope.Global:
                            {
                                _captureOutput._env.SetGlobalVariable(_captureOutput._assignment._varName, result);
                                break;
                            }
                    }
                }
            }
        }

        public override string GetCanonicalForm()
        {
            string key;
            switch (_scope)
            {
                case AssignmentScope.Local:
                    {
                        key = "local";
                        break;
                    }
                case AssignmentScope.Global:
                    {
                        key = "global";
                        break;
                    }
                default:
                    {
                        key = "assign";
                        break;
                    }
            }
            string block = NestedBlock == null ? "" : NestedBlock.GetCanonicalForm();
            return "<#" + key + " " + _varName +
                   (_namespaceExp != null ? " in " + _namespaceExp.GetCanonicalForm() : "")
                   + ">" + block + "</#" + key + ">";
        }

        public override string GetDescription()
        {
            return "block assignment to variable: " + _varName;
        }

        internal override bool IsIgnorable()
        {
            return false;
        }
    }
}